#include"Chunk.h"
#include"World.h"
#include"util\utils.h"

#include<math.h>
#include<fstream>
#include<functional>

namespace Craft
{
	namespace{


		const std::string chunk_output_file_name = "log/chunk.txt";
		std::ofstream chunk_ofile;
		std::ifstream chunk_ifile;

		const GLuint sky_lightValue = SKY_LIGHT;
	}
	/*
		@brief if we build a world to manger chunk,
			   the world will select a correct chunk,
			   but now ,it must to judeg the range

				准星方向与chunk的射线相交检测，返回准星所指向的方块和相交信息
	*/
	Block Chunk::intersection(const glm::vec3& orgin, const glm::vec3& dir, GLfloat range, HitInfo& hitInfo)
	{

		Block intert_block;
		GLfloat tMin = hitInfo.tMin_ ;
		int     fDir = hitInfo.hitDir_;

		glm::vec3 r_orgin = orgin - m_pos;

		if (r_orgin.x + range < 0 ||
			r_orgin.y + range < 0 ||
			r_orgin.z + range < 0 ||
			r_orgin.x - range  > m_width ||
			r_orgin.y - range  > m_width ||
			r_orgin.z - range  > m_width)
		{
			return intert_block;
		}

		GLuint low_x = (r_orgin.x - range) > 0 ? r_orgin.x - range : 0;
		GLuint low_y = (r_orgin.y - range) > 0 ? r_orgin.y - range : 0;
		GLuint low_z = (r_orgin.z - range) > 0 ? r_orgin.z - range : 0;

		GLuint upper_x = (r_orgin.x + range) < m_width ? r_orgin.x + range : m_width;
		GLuint upper_y = (r_orgin.y + range) < m_width ? r_orgin.y + range : m_width;
		GLuint upper_z = (r_orgin.z + range) < m_width ? r_orgin.z + range : m_width;

		for_range_glmVec3(glm::vec3(low_x,low_y,low_z), glm::vec3(upper_x,upper_y,upper_z), [&](const glm::vec3& pos)
		{
			int x = pos.x;int y = pos.y;int z = pos.z;

			Block block = m_blocks[x * m_width * m_width + y * m_width + z];
			if (block.get_Id() != 0)
			{
				if (block.intersection(r_orgin, dir, hitInfo) && hitInfo.tMin_ < tMin)
				{
					// 当tMin_ < tMin时，说明存在更加的碰撞面，更新hit信息
					tMin = hitInfo.tMin_;
					fDir = hitInfo.hitDir_;

					intert_block = block;
				}
			}
		});

		hitInfo.tMin_   = tMin;
		hitInfo.hitDir_ = fDir;
		return intert_block;
	}


	/*
		@brief 获取到与传入box发生碰撞的碰撞盒
	*/
	void  Chunk::get_hitBoxes(const AABB& box, std::vector<AABB>& hitBoxes)
	{
		glm::vec3 r_low   = box.m_min - m_pos - 1.5f;
		glm::vec3 r_upper = box.m_max - m_pos + 1.5f;

		if (r_upper.x  < 0 ||
			r_upper.y  < 0 ||
			r_upper.z  < 0 ||
			r_low.x  > m_width ||
			r_low.y  > m_width ||
			r_low.z  > m_width)
		{
			return ;
		}
		
		GLuint low_x = (r_low.x) > 0 ? r_low.x  : 0;
		GLuint low_y = (r_low.y) > 0 ? r_low.y  : 0;
		GLuint low_z = (r_low.z) > 0 ? r_low.z  : 0;

		GLuint upper_x = (r_upper.x ) < m_width ? r_upper.x  : m_width;
		GLuint upper_y = (r_upper.y ) < m_width ? r_upper.y  : m_width;
		GLuint upper_z = (r_upper.z ) < m_width ? r_upper.z  : m_width;

		for_range_glmVec3(glm::vec3(low_x, low_y, low_z), glm::vec3(upper_x, upper_y, upper_z), [&](const glm::vec3& pos)
		{
			int x = pos.x; int y = pos.y; int z = pos.z;

			// 当方块不为空气方块且发生碰撞时
			Block block = m_blocks[x * m_width * m_width + y * m_width + z];
			if (block.get_Id() != 0 && block.get_BoundingBox().intersect_AABB(box))
			{
				hitBoxes.push_back(block.get_BoundingBox());
			}
		});
	}

	Block& Chunk::get_block(const glm::vec3& pos)
	{
		int px = (int)(pos.x);
		int py = (int)(pos.y);
		int pz = (int)(pos.z);

		if (px >= 0 && px < m_width && py >= 0 && py < m_width && pz >= 0 && pz < m_width)
			return 	m_blocks[px * m_width * m_width + py * m_width + pz];
		return Block();
	}


	Block& Chunk::get_block_byWorldPos(const glm::vec3& pos)
	{
		int px = (int)(pos.x - m_pos.x);
		int py = (int)(pos.y - m_pos.y);
		int pz = (int)(pos.z - m_pos.z);

		if (px >= 0 && px < m_width && py >= 0 && py < m_width && pz >= 0 && pz < m_width)
			return 	m_blocks[px * m_width * m_width + py * m_width + pz];
		return Block();
	}

	/*
		@brief 设置chunk指定位置为传入block
			   每次设置方块，需要更新该方块的对应信息，包括该方块的位置信息和保存该方块chunk的chunk_id
	*/
	void Chunk::set_block(Block block, const glm::vec3& pos, bool updataAO , bool updateLight)
	{
		int px = (int)(pos.x);
		int py = (int)(pos.y);
		int pz = (int)(pos.z);

		// if set succedd, then set block's info
		block.set_pos(pos);
		block.set_chunk(this);

		if (px >= 0 && px < m_width && py >= 0 && py < m_width && pz >= 0 && pz < m_width)
		{
			m_blocks[px * m_width * m_width + py * m_width + pz] = block;
			m_update = true;
		
			// 一个chunk的更新也许会引起其他chunk的更新，例如边界线上的block顶点ao
			if (updataAO)
				for_range_glmVec3(glm::vec3(-1.0f), glm::vec3(2.0f), [&](const glm::vec3& offset)
				{
					Chunk* neighor_chunk = get_world()->get_chunk_byWorldPos(block.get_world_pos() + offset);
					if (neighor_chunk)
					{
						if (neighor_chunk->get_block_byWorldPos(block.get_world_pos() + offset).get_Id() > 0);
						neighor_chunk->set_update(true);
					}
				});

			// when adding a new block ,is must to re-calculate the lightValue of block
			if (updateLight)
				update_blockLight(sky_lightValue);
		}
	}

	void Chunk::set_block_byWorldPos(Block block, const glm::vec3& pos, bool updataAO , bool updateLight)
	{
		int px = (int)(pos.x - m_pos.x);
		int py = (int)(pos.y - m_pos.y);
		int pz = (int)(pos.z - m_pos.z);

		// if set succedd, then set block's info
		block.set_pos(glm::vec3(px,py,pz));		// 注意：block只保存相对坐标
		block.set_chunk(this);

		if (px >= 0 && px < m_width && py >= 0 && py < m_width && pz >= 0 && pz < m_width)
		{
			m_blocks[px * m_width * m_width + py * m_width + pz] = block;
			m_update = true;

			// 一个chunk的更新也许会引起其他chunk的更新，例如边界线上的block顶点ao
			if (updataAO)
				for_range_glmVec3(glm::vec3(-1.0f), glm::vec3(2.0f), [&](const glm::vec3& offset)
				{
					Chunk* neighor_chunk = get_world()->get_chunk_byWorldPos(block.get_world_pos() + offset);
					if (neighor_chunk)
					{
						if (neighor_chunk->get_block_byWorldPos(block.get_world_pos() + offset).get_Id() > 0);
						neighor_chunk->set_update(true);
					}
				});

			// when adding a new block ,is must to re-calculate the lightValue of block
			if (updateLight)
				update_blockLight(sky_lightValue);
		}
	}

	void Chunk::destory_block(const glm::vec3& pos, bool updataAO, bool updateLight)
	{
		int px = (int)(pos.x );
		int py = (int)(pos.y );
		int pz = (int)(pos.z );

		if (px >= 0 && px < m_width && py >= 0 && py < m_width && pz >= 0 && pz < m_width)
		{
			m_blocks[px * m_width * m_width + py * m_width + pz] = Block();
			m_update = true;

			if (updataAO)
				for_range_glmVec3(glm::vec3(-1.0f), glm::vec3(2.0f), [&](const glm::vec3& offset)
				{
					Chunk* neighor_chunk = get_world()->get_chunk_byWorldPos(this->get_pos() + pos + offset);
					if (neighor_chunk)
					{
						if (neighor_chunk->get_block_byWorldPos( this->get_pos() + pos + offset).get_Id() > 0);
						neighor_chunk->set_update(true);
					}
				});

			// when adding a new block ,is must to re-calculate the lightValue of block
			if (updateLight)
				update_blockLight(sky_lightValue);
		}
	}

	const glm::vec3 Chunk::get_pos()const
	{
		return m_pos;
	}

	const int Chunk::get_width()const
	{
		return m_width;
	}

	const int Chunk::get_count()const
	{
		return m_block_counts;
	}

	const int Chunk::get_id()const
	{
		return m_Id;
	}

	void Chunk::set_pos(const glm::vec3& pos)
	{
		m_pos = pos;
	}

	void Chunk::set_update(bool bupdate)
	{
		m_update = bupdate;
	}

	bool Chunk::is_update()const
	{
		return m_update;
	}


	World* Chunk::get_world()
	{
		return m_world;
	}

	/*
		@brief save current chunk to log/chunk.txt
	*/
	void Chunk::save_chunks()
	{
		if (!chunk_ofile.is_open())
		{
			chunk_ofile.open(chunk_output_file_name);
		}

		for_range_glmVec3(glm::vec3(0), glm::vec3(get_width()), [&](const glm::vec3& pos)
		{
			Block block = get_block(pos);
			chunk_ofile << block.get_Id() << " ";
		});

		chunk_ofile.close();

	}	


	/*
		@brief load  chunk from log/chunk.txt
	*/
	void Chunk::load_chunks()
	{
		if (!chunk_ifile.is_open())
		{
			chunk_ifile.open(chunk_output_file_name);
		}

		int id = 0;
		for_range_glmVec3(glm::vec3(0), glm::vec3(get_width()), [&](const glm::vec3& pos)
		{
			Block& block = get_block(pos);
			chunk_ifile >> id;
			set_block(Block(id), pos);
		});
		chunk_ifile.close();

		// 加载地图后更新环境光
		update_blockLight(sky_lightValue);
	}

	/*
		brief 更新该Chunk下所有block的lightvalue

			  每次更新做injection 和 propagation  // 暂时无用
	*/
	void Chunk::update_blockLight(GLuint skyLight)
	{
		// 1.injection
		for (size_t x = 0; x < m_width; x++)
		{
			for (size_t z = 0; z < m_width; z++)
			{
				injection(glm::vec3(x, m_width - 1, z),skyLight);
			}
		}

		// 2.propagation
		for (int i = 0; i < 10; i++)
			for_range_glmVec3(glm::vec3(0), glm::vec3(get_width()), [&](const glm::vec3& pos)
			{
				propagation(pos);
			});
		
	}

	/*
		brief 纵向从顶上第一个方块开始，向下所有透明方块亮度全部标记为 skylight
	*/
	void Chunk::injection(const glm::vec3& pos ,GLuint skyLight)
	{
		bool  bNoOcclusion = true;

		// 遍历y轴下所有块
		for (int y = pos.y; y >= 0; y--)
		{
			Block& currBlock = get_block(glm::vec3(pos.x,y,pos.z));

			// 只有当前块为透明块（包括空气块，且目前仅支持空气块）,设置为天空光
			if(bNoOcclusion && currBlock.get_Id() == 0)
			{
				currBlock.set_lightValue(skyLight);
			}
			else
			{
				bNoOcclusion = false;
				//currBlock.set_lightValue();
			}
	
		}// end for

	}

	/*
		@brief 传播方块亮度
	*/
	void Chunk::propagation(const glm::vec3& pos)
	{
		Block& currBlock = get_block(pos);

		// 如果为非透明方块，直接跳过
		if (currBlock.get_Id() > 0)
		{
			return;
		}

		GLuint max_light = 1;
		GLuint tmp_light = 1;

		if (currBlock.get_lightValue() > 0)
		{
			int i = 0;
		}

		for (int x = -1; x <= 1; x += 2)
		{
			Block tmp_block = get_block(glm::vec3(pos.x + x, pos.y, pos.z));
			tmp_light = tmp_block.get_lightValue() - tmp_block.get_lightOpacity();

			if (max_light < tmp_light)
			{
				max_light = tmp_light;
			}
		}
		
		for (int y = -1; y <= 1; y += 2)
		{
			Block tmp_block = get_block(glm::vec3(pos.x, pos.y + y, pos.z));
			tmp_light = tmp_block.get_lightValue() - tmp_block.get_lightOpacity();

			if (max_light < tmp_light)
			{
				max_light = tmp_light;
			}
		}
		
		for (int z = -1; z <= 1; z += 2)
		{
			Block tmp_block = get_block(glm::vec3(pos.x, pos.y, pos.z + z));
			tmp_light = tmp_block.get_lightValue() - tmp_block.get_lightOpacity();

			if (max_light < tmp_light)
			{
				max_light = tmp_light;
			}
		}

		// 设置当前方块的亮度为max_light
		currBlock.set_lightValue(max_light);
	}
}